#!/usr/bin/python

import os
import uuid
import re
import urllib.parse
import logging
import mimetypes
from PIL import Image
from io import BytesIO
from abc import abstractmethod

logger = logging.getLogger(__name__)

# 本地存储路径，upload模块用
SITE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
LOCAL_STORAGE_PATH = os.path.join(SITE_ROOT, 'static')
STATIC_PATH = os.path.join(LOCAL_STORAGE_PATH, 'tmp')

FILEINFO_POLICY_LOCAL = 0
FILEINFO_POLICY_OSS = 1

FILEINFO_POLICY_LIST = [
    (FILEINFO_POLICY_LOCAL, u'本地存储'),
    (FILEINFO_POLICY_OSS, u'OSS存储'),
]


class BaseStorage(object):
    Write = 0  # 覆盖写
    Append = 1  # 追加写

    def __init__(self, **kwargs):
        """
        子类不要重写__init__方法，初始化代码在setup中实现
        """
        self.setup(**kwargs)
        # _register_storage(self)

    @abstractmethod
    def setup(self, **kwargs):
        pass

    @abstractmethod
    def upload(self, path, data, mode=Write):
        """
        @params path: 上传路径
        @params data: 上传数据
        @params mode: 写模式 Write/Append
        """
        pass

    @abstractmethod
    def download(self, path):
        pass

    @abstractmethod
    def delete(self, path):
        pass

    @abstractmethod
    def image_resize(self, path, scale):
        """
        图片缩放
        @params path: 原始图片的路径
        @params scale: 缩放比
        @return : 图片路径，新图片数据
        """
        data = self.download(path)
        bio = BytesIO(data)
        try:
            img = Image.open(bio)
        except Exception:
            raise TypeError("cannot recognize image format")
        width = int(img.width * scale)
        height = int(img.height * scale)
        if width == 0 or height == 0:
            raise ValueError("imageScale to small")
        out_buf = BytesIO()
        img = img.resize((width, height), Image.ANTIALIAS)
        img.save(out_buf, format='png')
        new_data = out_buf.getvalue()
        return None, new_data


class LocalStorage(BaseStorage):
    def setup(self, **kwargs):
        self.__tmp_dir = LOCAL_STORAGE_PATH + '/tmp/'
        if not os.path.exists(self.__tmp_dir):
            os.makedirs(self.__tmp_dir)

    def parse_path(self, path):
        if not path.startswith('/'):
            return '/'.join((LOCAL_STORAGE_PATH, path))
        elif path.startswith(STATIC_PATH):
            return LOCAL_STORAGE_PATH + path[len(STATIC_PATH) - 1:]
        else:
            return LOCAL_STORAGE_PATH + path

    def object_exists(self, path: str) -> bool:
        path = self.parse_path(path)
        if not os.path.exists(path):
            return False
        else:
            return True

    def upload(self, path, data, mode=BaseStorage.Write, expire: int = None):
        path = self.parse_path(path)
        if isinstance(data, bytes):
            mode = 'ab' if mode == self.Append else 'wb'
        elif isinstance(data, str):
            mode = 'a' if mode == self.Append else 'w'
        if mode == self.Append:
            with open(path, mode) as f:
                f.write(data)
        elif not os.path.exists(path):
            tmp_path = self.__tmp_dir + str(uuid.uuid4())
            with open(tmp_path, mode) as f:
                f.write(data)
            dst_dir = os.path.dirname(path)
            if not os.path.exists(dst_dir):
                try:
                    os.makedirs(dst_dir)
                except FileExistsError as e:
                    if not os.path.exists(dst_dir):
                        os.makedirs(dst_dir)
            os.rename(tmp_path, path)
        elif os.path.exists(path):
            with open(path, mode) as f:
                f.write(data)

    def download(self, path):
        path = self.parse_path(path)
        with open(path, 'rb') as f:
            return f.read()

    def download_url(self, file_path):
        if re.findall('[\u4e00-\u9fa5]', file_path):
            file_path = os.path.join(STATIC_PATH, file_path)
            return file_path
        return os.path.join(STATIC_PATH, urllib.parse.quote(file_path))

    def download_raw_url(self, file_path):
        return self.download_url(file_path)

    def delete(self, path):
        try:
            os.remove(self.parse_path(path))
        except Exception:
            logger.exception('remove file %s failed' % path)

    def get_file_meta(self, path):
        """
        获取文件元信息
        :param path:
        :return: mtime, mimetypes, file_length

        mtime: 文件最后修改时间戳，类型为int，可能是None。
        mimetypes.MimeTypes(): 文件的MIME类型，可能是None
        file_length(bytes): Content-Length，可能是None
        """
        path = self.parse_path(path)
        try:
            mtime = os.path.getmtime(path)
            file_size = os.path.getsize(path)
        except FileNotFoundError:
            mtime = None
            file_size = None
        return mtime, mimetypes.MimeTypes().guess_type(path)[0], file_size
